White Knight 11 has a very special type of feature that adds a tremendous amount of flexibility to your Procedure files. This feature allows White Knight 11's Procedure language to be extended with user-written commands, much like Hypercard's XCMD's. In fact, White Knight's user-written commands are given the generic name "RCMD" (pronounced "r-command").
What Is An RCMD?
An RCMD is simply an executable code resource, with the resource type 'RCMD', much like the types 'MDEF' and 'WDEF' described in Inside Macintosh. If your particular language compiler can create a 'MDEF' resource, it is almost certain that you can use it to create an 'RCMD' resource. The 'RCMD' resource, along with any other resources the RCMD might need, is placed in the resource fork of the Procedure which calls it. A Procedure File can contain as many different RCMD's as you wish, but any conflicts between resource numbers are entirely up to you to resolve. Therefore, if you are developing RCMD's that might be of use to others, you might want to consider putting each RCMD in a Procedure File of its own, terminated with a NESTEND command. By doing this, Procedure developers could take advantage of the NEST command to call your RCMD without worrying about resource number conflicts with other RCMD's in the same Procedure file.
The RCMD is called Pascal style with one longint parameter as an argument. This is a pointer to a special parameter block containing many different variables important to White Knight (and perhaps your RCMD). No parameters are returned by the RCMD.
The Mechanics Of Calling An RCMD
For a Procedure to call an RCMD, it is necessary to use only one special Procedure command:
RCMD num_exp1,num_exp2
Description: This command executes the RCMD whose resource ID number is contained in num_exp2. The RCMD itself is contained in the resource fork of the Procedure file. The parameter num_exp1 will be either 0 (zero) or 1 (one).
If the RCMD opens any windows or installs any menus, it must pass zero for num_exp1. This tells White Knight to close its windows and deinstall its menus before executing the RCMD. This is necessary because it would be impossible to update or activate/deactivate any of White Knight's windows from within the RCMD, and your RCMD would not know the present state of any of White Knight's menus. When the RCMD completes, White Knight's windows and menus are redisplayed as before. If your RCMD does not require any user input/output and does not do any drawing, you can pass a 1 for num_exp1 and the RCMD will be quite transparent to the user.
The sample RCMD's provided with White Knight 11 show the versatility of this feature. RCMD's can manipulate their own menus, support desk accessories and MultiFinder, and provide their own graphic interface to
the user.
The Down And Dirty Aspects Of RCMD's
If an RCMD were documented in Inside Macintosh style, it would appear like this:
MyRCMD(RPtr: params);
in C language, it would be documented like this:
pascal void MyRCMD(params)
RPtr params;
The RPtr parameter is a pointer to a structure that's rather complex and includes most everything I could think of that you might want access to in your routines. PLEASE DON'T BE PUT OFF BY THE SIZE OF THIS THING! Your RCMD might not have any need of any of the following, in which case you could just ignore the RPtr value that's passed.
In C language format, the structure would look like this:
typedef struct
{
NumVarPtr Numeric;
StrVarPtr String;
WKPtr WKParams;
Ptr Reserved1;
long Reserved2;
long Reserved3;
long Reserved4;
short Reserved5;
ParmBlkPtr UserBlock1;
ParmBlkPtr UserBlock2;
ParmBlkPtr SerialIn;
ParmBlkPtr SerialOut;
Byte20Ptr RadioGroup;
Byte20Ptr CheckBox;
Ptr1K Buffer1K;
Ptr2K Buffer2K;
FilterPtr TFilter;
FilterPtr FFilter;
FilterPtr PFilter;
FlagPtr Flags;
Ptr123 RecPath;
RGBColor Forecolor;
RGBColor Backcolor;
RGBColor Hilcolor;
RGBColor SBfore;
RGBColor SBback;
RGBColor SBhil;
RGBColor Phonefore;
RGBColor Phoneback;
RGBColor Phonehil;
RGBColor Indfore;
RGBColor Indback;
RGBColor Rlefore;
RGBColor Rleback;
WindowPeek WKsWindow;
short DoUpdate;
Rect TransferRect;
short Version;
} RPtr,*RParam;
We'll now look at each of the components of this structure.
Numeric
This is a pointer to a structure that looks like this:
typedef struct
{
long thenum[234];
} NumVar,*NumVarPtr;
The parameter thevar is an array of 234 long integers, each containing the current values of the numeric variables A1% to Z9% in that order. The structure is ordered so that the first 26 elements are A1% through Z1%, the next 26 elements would be A2% through Z2%, and the last 26 elements would be A9% through Z9%.
String
This is a pointer to a structure that looks like this:
typedef struct
{
Byte thestr[234][134];
} Strings,*StrVarPtr;
The parameter thestr is an array of 234 Pascal type strings of 134 characters each (including the length byte), containing the current contents of the string variables A1$ to Z9$ in that order. The structure is ordered so that the first 26 elements are A1$ through Z1$, the next 26 elements would be A2$ through Z2$, and the last 26 elements would be A9$ through Z9$.
WKParam
This is a pointer to a structure that looks like this:
typedef struct
{
Byte theparam[8350];
} WKParam,*WKPtr;
The parameter theparam is an array of 8350 bytes, which corresponds to the values you would use with the Procedure command GETPARAM and PUTPARAM. The same rules as to reading or writing these values must be followed as for the GETPARAM and PUTPARAM commands.
These values are used internally and should not be used or written to by your RCMD.
UserBlock1 and UserBlock2
The ParmBlkPtr structure is documented on page II-98 of Inside Macintosh. UserBlock1 and UserBlock2 point to the two structures used with the Procedure commands USEROPENI, USEROPENO, USEROPENA, USERCLOSE, USERWRITE, USERREAD, and USERWRCR (corresponding to the two paths available to these commands).
SerialIn and SerialOut
The ParmBlkPtr structure is documented on page II-98 of Inside Macintosh. SerialIn points to the structure White Knight has allocated for reading input from the currently open serial port. SerialOut points to the structure White Knight has allocated for writing to the currently open serial port.
RadioGroup And CheckBox
These two parameter point to a structure that looks like this:
typedef struct
{
Byte theval[20];
} Byte20,*Byte20Ptr;
The structure pointed to by RadioGroup structure represents the 20 bytes allocated for use with the GETGROUP and SETGROUP Procedure commands. The structure pointed to by CheckBox represents the 20 bytes allocated for use with the SETBOX and GETBOX Procedure commands.
Buffer1K and Buffer2K
Buffer1K points to a structure that looks like this:
typedef struct
{
Byte data[1024];
} Buff1K,*Ptr1K;
Buffer2K points to a structure that looks like this:
typedef struct
{
Byte data[2048];
} Buff2K,*Ptr2K;
These two items point to buffers of 1K bytes and 2K bytes allocated by White Knight. You are free to use them for any purpose you wish. They are especially attractive if free memory is unknown or known to be small.
TFilter, FFilter, And PFilter
These three parameters point to a structure that looks like this:
typedef struct
{
Byte thechar[256][2];
} Filter,*FilterPtr;
This structure represents a 2 byte entry for each of the possible 256 ASCII codes. The first byte tells you how to filter received bytes of that value. 0 means let it pass through, 1 means strip it out, 2 means to replace it with a byte of the value in the 2nd byte entry. and 3 means to enumerate it. TFilter points to the currently installed Terminal Filter. FFilter points to the currently installed File Capture Filter. PFilter points to the currently installed Protocol Transfer Filter.
Flags
This parameter points to a structure that looks like this:
typedef struct
{
Byte YesNoFlag;
Byte ErrorFlag;
} FlagRec,*FlagPtr;
The YesNoFlag flag is used by the IF YES and IF NO Procedure commands. Set it to zero to mean NO or one to mean YES. The ErrorFlag is used by the IF ERROR and IF NO ERROR Procedure commands. Set it to zero to mean NO ERROR or one to mean ERROR.
RecPath
This parameter points to a structure that looks like this:
typedef struct
{
Byte thepath[124];
} Byte124,*Ptr124;
The parameter thepath is a Pascal type string of up to 124 characters (including the length byte) that contains the current Received File Destination path.
Colors
There are 13 parameters that are structures (not pointers to structures) that looks like this:
typedef struct
{
unsigned short red;
unsigned short green;
unsigned short blue;
} RGBColor;
This structure is described on page V-48 of Inside Macintosh. They are the colors used by White Knight for the following purposes:
Forecolor: terminal window foreground color.
Backcolor: terminal window background color.
Hilcolor: terminal window hilite color.
SBfore: status bar foreground color.
SBback: status bar background color.
SBhil: status bar hilite color.
Phonefore: phonebook foreground color.
Phoneback: phonebook background color.
Phonehil: phonebook hilite color.
Indfore: file transfer indicator foreground color.
Indback: file transfer indicator background color.
Rlefore: RLE graphics foreground color.
Rleback: RLE graphics background color.
These colors would be used by your RCMD to provide a consistent interface to the user. For instance, in the "QUICKB" RCMD, I use both the Indfore and Indback values to stay consistent with what White Knight's built in protocols would use.
WKsWindow
The WindowPeek structure is documented on page I-304 of Inside Macintosh. Note that if White Knight is running on a machine that supports Color Quickdraw, this parameter will be a CWindowPeek type parameter, which is documented on page V-199 of Inside Macintosh. WKsWindow is simply a pointer to the terminal window.
DoUpdate
This parameter is an integer. If your RCMD does any drawing in WKsWindow, it should set this parameter to non-zero, so that White Knight will correctly redraw the terminal window when your RCMD finishes.
TransferRect
This parameter is a Rect structure, which is described on page I-141 of Inside Macintosh. This parameter is the global coordinates of the last position of the file transfer window, should you decide to try and duplicate the appearance of that window.
Version
This integer will hold a version number of the structure pointed to by RPtr so that additional items can be added in future versions of White Knight. The version number for the structure described in this document is zero.
Register Usage
LightSpeed C users will not have to do anything special, but for assembly language (and some other languages), programmers may destroy the contents of only registers D0,D1,D2, A0, and A1. The contents of all other registers must be preserved. Remember that your resource is being called with the RPtr argument being passed Pascal style, just as if your resource was a MDEF or WDEF.. If your RCMD uses global variables, you'll probably need to do something to make the A5 or A4 register point to them upon entry to your RCMD. In LightSpeed C, for instance, global variables in a CODE resource are referenced off of A4, so the LightSpeed C routines RememberA0, SetUpA4, and RestoreA4 are used at the beginning and end (respectively) of the RCMD that uses global variables.
Sample RCMD Code
The following is a small RCMD that demonstrates how such a thing is put together, as well as how to access White Knight 11's variables. All of the following is for LightSpeed C.
/* File Speak.c */
#include "MacTypes.h"
#include "MemoryMgr.h"
#include "OSUtil.h"
#include "pascal.h"
#include "SegmentLdr.h"
#include "ToolboxUtil.h"
#include "Macintalk.h"
#include "QuickDraw.h"
#include "DeviceMgr.h"
#include "FileMgr.h"
#include "WindowMgr.h"
/* Lightspeed C sez this should always be the last #included file */
#include "SetUpA4.h"
typedef struct
{
long thenum[26];
} NumVar,*NumVarPtr;
typedef struct
{
Byte thestr[26][134];
} Strings,*StrVarPtr;
typedef struct
{
Byte theparam[8350];
} WKParam,*WKPtr;
typedef struct
{
Byte theval[20];
} Byte20,*Byte20Ptr;
typedef struct
{
Byte data[1024];
} Buff1K,*Ptr1K;
typedef struct
{
Byte data[2048];
} Buff2K,*Ptr2K;
typedef struct
{
Byte thechar[256][2];
} Filter,*FilterPtr;
typedef struct
{
Byte YesNoFlag;
Byte ErrorFlag;
} FlagRec,*FlagPtr;
typedef struct
{
Byte thepath[124];
} Byte124,*Ptr124;
typedef struct
{
unsigned short red;
unsigned short green;
unsigned short blue;
} RGBColor;
typedef struct
{
NumVarPtr Numeric;
StrVarPtr String;
WKPtr WKParams;
Ptr Reserved1;
long Reserved2;
long Reserved3;
long Reserved4;
short Reserved5;
ParmBlkPtr UserBlock1;
ParmBlkPtr UserBlock2;
ParmBlkPtr SerialIn;
ParmBlkPtr SerialOut;
Byte20Ptr RadioGroup;
Byte20Ptr CheckBox;
Ptr1K Buffer1K;
Ptr2K Buffer2K;
FilterPtr TFilter;
FilterPtr FFilter;
FilterPtr PFilter;
FlagPtr Flags;
Ptr124 RecPath;
RGBColor Forecolor;
RGBColor Backcolor;
RGBColor Hilcolor;
RGBColor SBfore;
RGBColor SBback;
RGBColor SBhil;
RGBColor Phonefore;
RGBColor Phoneback;
RGBColor Phonehil;
RGBColor Indfore;
RGBColor Indback;
RGBColor Rlefore;
RGBColor Rleback;
WindowPeek WKsWindow;
short DoUpdate;
Rect TransferRect;
short Version;
} RParam,*RPtr;
/* Globals used in this resource */
short SPitch,SRate;
Handle MTHand;
short OSerr;
Handle SHand;
short z;
/*
Speak text in T1$ using Macintalk, using SpeechPitch P1%, SpeechRate R1%, and Exceptions File F1$ (which should be blank if no exceptions). M1% specifies the mode: 0 = Natural, 1 = Robotic.
*/
/* Start of code resource */
pascal void main(params)
RPtr params;
{
/*
Following two instructions preserve the contents of register A4
coming in and set up A4 to point to our globals. Yes, this could have
been done with local variables, but I want to demonstrate that globals
are possible if your compiler supports them with CODE resources like
LightSpeed C. RememberA0(), SetUpA4(), and RestoreA4() are LightSpeed
C private functions - your compiler may not support them or there may be equivalent functions for accessing global variables inside of a CODE resource.
/* Folowing instruction restores register A4 to what it was before we entered here. */
RestoreA4();
}
/* End of Speak.c */
Putting This Together
In LightSpeed C, you should create a new project containing the libraries "MacTraps", and "Macintalk.Lib" and the source code file "Speak.c" (above). Go to LightSpeed C's "Project" menu and choose "Set Project Type...". In the dialog box that comes up, you should select the radio button marked "Code resource". For the "Type" item, type in "RCMD". For the "ID" item, type in the number "202". Now under the "Project" menu, choose "Build Code Resource". After the libraries are loaded and the source code file is compiled, you'll be prompted to enter a filename the resource is to be placed in. Type in "Speak.CODE".
Now, we'll need to build a Procedure file to set up the proper variables and call our RCMD. The following Procedure commands should be entered, and compiled to the file "Speak.PROC".
(Speak text in T$, using SpeechPitch P%, SpeechRate R%, and Exceptions)
COPYINTO T$,This is a test of White Knight speaking to me.
RCMD 1,202
NESTEND
Finally, using ResEdit, copy the RCMD resource from "Speak.CODE" to "Speak.PROC". In White Knight 11, run the Procedure "Speak.PROC" to see how it works.
Using Resources With RCMD's
Although our sample RCMD above doesn't do any window or menu manipulation, this is entirely possible (check out ProcEdit, which even supports desk accessories). If your RCMD requires any resources, simply copy them into the Procedure file's resource fork along with the RCMD resource. HINT: once the RCMD is developed and you will be doing further development only on the Procedure source code, you can save a lot of time by copying the RCMD resource (and any resources the RCMD uses) to the source code file of the Procedure. This is because White Knight's Procedure compiler will automatically copy any resources in the source code file to the executable Procedure file and you'll therefore save a step with ResEdit for each compile.
Special Warning
If you choose to support an Apple menu, it must be given a menu ID number of 201 and it must begin with an "About..." item followed by a disabled gray line. If these rules are not observed, your RCMD will not support MultiFinder and desk accessories correctly.